//RMIServer.java
package awk.javarmi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import java.net.URL;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/**
 * 创建一个RMI应用包括以下步骤：
 * （1）创建远程接口：继承java.rmi.Remote接口。
 * （2）创建远程类：实现远程接口。
 * （3）创建服务器程序：创建远程对象，通过createRegistry()方法注册远程对象。并通过bind或者rebind方法，把远程对象绑定到指定名称空间（URL）中。
 * （4）创建客户程序：通过 lookup()方法查找远程对象，进行远程方法调用 
 */
public class RMIServer {
    public static void main(String[] args) {
        try {
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
            //如果需要使用RMI的动态加载功能，需要开启RMISecurityManager，并配置policy以允许从远程加载类库
            URL resource = RMIServer.class.getClassLoader().getResource("java.policy");
            String file = resource.getFile();
            System.setProperty("java.security.policy", file);
            RMISecurityManager securityManager = new RMISecurityManager();
            System.setSecurityManager(securityManager);

            Registry reg;
            try {
                reg = LocateRegistry.createRegistry(9999); // 在本地主机上创建和导出注册表实例，并在指定的端口上接受请求
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            //方式一
            Reference refObj = new Reference("ExportObject", "awk.javarmi.ExportObject", "http://127.0.0.1:8000/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
            reg.bind("refObj", refObjWrapper);
            //方式二 UnicastRemoteObject
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            //Services services = (Services) UnicastRemoteObject.exportObject(new ServicesImpl(), 0);
            Services services = new ServicesImpl();
            reg.bind("Services", services);
        } catch (RemoteException | AlreadyBoundException | NamingException e) {
            e.printStackTrace();
        }
    }

  /*  当我们用一个对象作为远程方法调用的参数时，对象是以序列化流来传输到远端，然后在远端重新生成对象。这样就可能在两个Java虚拟机中交换对象了。但是序列化是这种传递对象的一部分。
  当你序列化对象时，你仅仅是把对象的成员数据转化成字节流，而实际实现该对象的代码却没有。也就是说，传递的只是数据部分，而做为控制逻辑的程序代码部分却没有被传递。
  这就是RMI初学者轻易误解的地方，我已经序列化对象了，而且对象也传过去了，怎么还说找不到呢。其实，对象数据的确过去了，不过找不到是类定义，这个并不是序列化传过去的，
  RMI协议是不传递代码的。但是，对于本地没有的类文件的对象，RMI提供了一些机制答应接收对象的一方去取回该对象的类代码。而到什么地方去取，这就需要发送方设置codebase了。 

    二、什么是codebase
    简单说，codebase就是远程装载类的路径。当对象发送者序列化对象时，会在序列化流中附加上codebase的信息。 这个信息告诉接收方到什么地方寻找该对象的执行代码。
    你要弄清楚哪个设置codebase，而哪个使用codebase。任何程序假如发送一个对方可能没有的新类对象时就要设置codebase（例如jdk的类对象，就不用设置codebase）。 
    codebase实际上是一个url表，在该url下有接受方需要下载的类文件。假如你不设置codebase，那么你就不能把一个对象传递给本地没有该对象类文件的程序。 

    三、怎样设置codebase
    在大多数情况下，你可以在命令行上通过属性java.rmi.server.codebase来设置codebase。例如，假如你在机器url上运行web服务器，端口是8080，你所提供下载的类文件在webserver的根目录下。那么运行应用程序的java 命令行: 
    -Djava.rmi.server.codebase=http://url:8080/  这就是告诉任何接受对象的程序，假如本地没有类文件，可以从这个url下载。
     System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
     */

}